﻿using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Encoders;
using System;
using System.Text;

namespace BuddHa.Common.SMUtil.Implementation.V1
{
    internal class SMV1Dispatch : ISM2Dispatch
    {
        private ECPrivateKeyParameters ecPriv;
        private ECPublicKeyParameters ecPub;
        private ECPoint g;
        private string UserId;
        private int _byteLen;
        public SMV1Dispatch(string userId, string privateKey, string publicKey = "", SMInEncodingTypes smInEncodingTypes = SMInEncodingTypes.Hex)
        {
            UserId = userId;
            BigInteger SM2_ECC_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16);
            BigInteger SM2_ECC_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16);
            BigInteger SM2_ECC_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16);
            BigInteger SM2_ECC_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16);
            BigInteger SM2_ECC_H = BigInteger.ValueOf(4);
            BigInteger SM2_ECC_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
            BigInteger SM2_ECC_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);

            ECCurve curve = new FpCurve(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H);

            g = curve.CreatePoint(SM2_ECC_GX, SM2_ECC_GY);
            ECDomainParameters domainParams = new ECDomainParameters(curve, g, SM2_ECC_N);

            #region 重建私钥
            if (!string.IsNullOrWhiteSpace(privateKey))
            {
                //BigInteger bi = new BigInteger(HexStrToToByte(privateKey));
                BigInteger bi = new BigInteger(privateKey, 16);
                ecPriv = new ECPrivateKeyParameters(bi, domainParams);
            }

            #endregion

            #region 重建公钥(有问题，待修改)
            if (!string.IsNullOrWhiteSpace(publicKey))
            {
                //byte[] pc = HexStrToToByte("04" + publicKey); //只处理未压缩的公钥
                //var basePoint = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey(null, domainParams, pc));
                //SubjectPublicKeyInfo subinfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(basePoint);
                //ecPub = (ECPublicKeyParameters)PublicKeyFactory.CreateKey(subinfo);
                if (publicKey.Length == 128)
                {

                    //只处理未压缩的公钥
                    var publicKeyPoint = curve.CreatePoint(new BigInteger(publicKey.Substring(0, 64), 16), new BigInteger(publicKey.Substring(64, 64), 16));
                    ecPub = new ECPublicKeyParameters(publicKeyPoint, domainParams);
                }
                else if (publicKey.Length == 66)
                {

                }
            }
            #endregion
            this._byteLen = (int)Math.Ceiling((double)(((double)curve.FieldSize) / 8.0));
        }

        /// <summary>
        /// 解密
        /// </summary>
        /// <param name="data"></param>
        /// <param name="padding"></param>
        /// <returns></returns>
        public string Decrypt(string data, Encoding encoding, SMInEncodingTypes smInEncodingTypes = SMInEncodingTypes.Hex)
        {
            SM2Engine sm2 = new SM2Engine();
            sm2.Init(false, ecPriv);
            byte[] msg = Hex.Decode(data);
            byte[] decryptByte = sm2.Decrypt(msg, 0);
            return Encoding.Default.GetString(decryptByte);
        }

        /// <summary>
        /// 加密
        /// </summary>
        /// <param name="data"></param>
        /// <param name="padding"></param>
        /// <returns></returns>
        public string Encrypt(string data, Encoding encoding, SMInEncodingTypes smInEncodingTypes = SMInEncodingTypes.Hex)
        {
            SM2Engine sm2 = new SM2Engine();
            sm2.Init(true, new ParametersWithRandom(ecPub, new SecureRandom()));
            byte[] msg = Strings.ToByteArray(data);
            byte[] encryptByte = sm2.Encrypt(msg, 0);

            return Encoding.Default.GetString(Hex.Encode(encryptByte));
        }

        /// <summary>
        /// 签名
        /// </summary>
        /// <param name="data"></param>
        /// <param name="hashAlgorithmName"></param>
        /// <param name="padding"></param>
        /// <returns></returns>
        public string Sign(string data, Encoding encoding, SMInEncodingTypes smInEncodingTypes = SMInEncodingTypes.Hex, SMOutEncodingTypes smOutEncodingTypes = SMOutEncodingTypes.Hex)
        {
            byte[] dataBuffer = null;
            if (smInEncodingTypes == SMInEncodingTypes.Hex)
            {
                dataBuffer = HexStrToToByte(data);//传入16进制待签名内容用这个
            }
            else if (smInEncodingTypes == SMInEncodingTypes.Base64)
            {
                dataBuffer = Strings.ToByteArray(data);//传入非16进制待签名内容用这个 
            }

            byte[] sig = Sign(dataBuffer);

            BigInteger[] rs = Decode(sig);

            //Stopwatch sw = new Stopwatch();
            //sw.Start();

            string signHexStr = HexUtil.ToHexString(BigIntegerToByteArray(rs[0]), HexUtil.SpacePosition.Back) + HexUtil.ToHexString(BigIntegerToByteArray(rs[1]), HexUtil.SpacePosition.Back);


            //sw.Stop();
            //TimeSpan ts1 = sw.Elapsed;
            //Console.WriteLine("signHexStr is: {0}ms", ts1.TotalMilliseconds);
            //Asn1Sequence s = Asn1Sequence.GetInstance(sig);

            //byte[] rb = DerInteger.GetInstance(s[0]).Value.ToByteArrayUnsigned();
            //byte[] sb = DerInteger.GetInstance(s[1]).Value.ToByteArrayUnsigned();

            //string signHexStr = byteToHexStr(rb, rb.Length) + byteToHexStr(sb, sb.Length);   
            if (smOutEncodingTypes == SMOutEncodingTypes.Hex)
            {
                return signHexStr;
            }
            else if (smOutEncodingTypes == SMOutEncodingTypes.Base64)
            {
                throw new Exception("签名失败，暂时不支持，请使用V2");
            }
            else
            {
                throw new Exception("签名失败，未知的编码类型");
            }
        }

        public byte[] Sign(byte[] data)
        {
            if (ecPriv == null)
            {
                throw new Exception("no ecPriv");
            }
            SM2Signer signer = new SM2Signer();

            signer.Init(true,
               new ParametersWithID(new ParametersWithRandom(ecPriv, new SecureRandom()), Strings.ToByteArray(this.UserId)));
            //new ParametersWithID(new ParametersWithRandom(ecPriv, CreateFixedSecureRandom("6CB28D99385C175C94F94E934817663FC176D925DD72B727260DBAAE1FB2F96F")), Strings.ToByteArray(this.UserId)));

            //Stopwatch sw = new Stopwatch();
            //sw.Start();

            //sw.Stop();
            //TimeSpan ts = sw.Elapsed;
            //Console.WriteLine("HexStrToToByte is: {0}ms", ts.TotalMilliseconds);

            signer.BlockUpdate(data, 0, data.Length);

            //sw.Restart();

            byte[] sig = signer.GenerateSignature();

            //sw.Stop();
            //TimeSpan ts1 = sw.Elapsed;
            //Console.WriteLine("GenerateSignature is: {0}ms", ts1.TotalMilliseconds);

            //System.Console.Write("1");
            return sig;
        }

        public string ConvertStringToHex(string strASCII, string separator = null)
        {
            StringBuilder sbHex = new StringBuilder();
            foreach (char chr in strASCII)
            {
                sbHex.Append(String.Format("{0:X2}", Convert.ToInt32(chr)));
                sbHex.Append(separator ?? string.Empty);
            }
            return sbHex.ToString();
        }

        public bool VerifySign(string data, string sign, Encoding encoding, SMInEncodingTypes smInEncodingTypes = SMInEncodingTypes.Hex, SMOutEncodingTypes smOutEncodingTypes = SMOutEncodingTypes.Hex)
        {

            byte[] msg = null;
            byte[] signBuffer = null;
            if (smInEncodingTypes == SMInEncodingTypes.Hex)
            {
                msg = HexStrToToByte(data); //传入16进制待签名内容用这个
            }
            else if (smInEncodingTypes == SMInEncodingTypes.Base64)
            {
                msg = Strings.ToByteArray(data); //传入非16进制待签名内容用这个            
            }
            else
            {
                throw new Exception("验签失败，未知的编码类型");
            }
            if (smOutEncodingTypes == SMOutEncodingTypes.Hex)
            {
                signBuffer = HexUtil.FromHexString(sign);
            }
            else if (smOutEncodingTypes == SMOutEncodingTypes.Base64)
            {
                signBuffer = Convert.FromBase64String(sign);
            }
            else
            {
                throw new Exception("验签失败，未知的编码类型");
            }
            return VerifySign(msg, signBuffer);
        }

        /// <summary>
        /// 验签
        /// </summary>
        /// <param name="data"></param>
        /// <param name="sign"></param>
        /// <param name="hashAlgorithmName"></param>
        /// <param name="padding"></param>
        /// <returns></returns>
        public bool VerifySign(byte[] data, byte[] sign)
        {
            if (ecPub == null)
            {
                throw new Exception("SM Verify failed, no ecPriv");
            }
            //Stopwatch sw = new Stopwatch();
            //sw.Start();
            SM2Signer signer = new SM2Signer();
            signer.Init(false, new ParametersWithID(ecPub, Strings.ToByteArray(this.UserId)));


            //sw.Stop();
            //TimeSpan ts = sw.Elapsed;
            //Console.WriteLine("HexStrToToByte is: {0}ms", ts.TotalMilliseconds);
            signer.BlockUpdate(data, 0, data.Length);

            var signString = HexUtil.ToHexString(sign);
            BigInteger rbb = new BigInteger(signString.Substring(0, 64), 16);
            BigInteger sbb = new BigInteger(signString.Substring(64, 64), 16);

            //sw.Restart();
            bool res = signer.VerifySignature(rbb, sbb);
            //sw.Stop();
            //TimeSpan ts1 = sw.Elapsed;
            //Console.WriteLine("VerifySignature is: {0}ms", ts1.TotalMilliseconds);
            //System.Console.Write("2");
            return res;
        }

        public static SM2V1Key CreateKey()
        {
            BigInteger SM2_ECC_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16);
            BigInteger SM2_ECC_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16);
            BigInteger SM2_ECC_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16);
            BigInteger SM2_ECC_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16);
            BigInteger SM2_ECC_H = BigInteger.ValueOf(4);
            BigInteger SM2_ECC_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
            BigInteger SM2_ECC_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
            return CreateKey(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H, SM2_ECC_GX, SM2_ECC_GY);
        }

        public static SM2V1Key CreateKey(BigInteger SM2_ECC_P, BigInteger SM2_ECC_A, BigInteger SM2_ECC_B, BigInteger SM2_ECC_N, BigInteger SM2_ECC_H, BigInteger SM2_ECC_GX, BigInteger SM2_ECC_GY)
        {


            ECCurve curve = new FpCurve(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H);

            ECPoint g1 = curve.CreatePoint(SM2_ECC_GX, SM2_ECC_GY);

            ECDomainParameters domainParams = new ECDomainParameters(curve, g1, SM2_ECC_N);

            ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParams, new SecureRandom());
            ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();

            keyPairGenerator.Init(keyGenerationParams);
            AsymmetricCipherKeyPair kp = keyPairGenerator.GenerateKeyPair();

            ECPrivateKeyParameters ecPriv1 = (ECPrivateKeyParameters)kp.Private;
            ECPublicKeyParameters ecPub1 = (ECPublicKeyParameters)kp.Public;

            int bytelength = (int)Math.Ceiling((double)(((double)curve.FieldSize) / 8.0));

            //keys.Add(byteToHexStr(ecPriv1.D.ToByteArrayUnsigned(), 32));
            //keys.Add(ecPub1.Q.XCoord.ToString().ToUpper() + ecPub1.Q.YCoord.ToString().ToUpper());

            byte[] prikByte = BigIntegerToByteArray(ecPriv1.D, bytelength);

            var sm2V1Key = new SM2V1Key();
            sm2V1Key.PrivateKey = HexUtil.ToHexString(prikByte, HexUtil.SpacePosition.Back);
            sm2V1Key.PublicKey = HexUtil.ToHexString(BigIntegerToByteArray(ecPub1.Q.XCoord.ToBigInteger(), bytelength), HexUtil.SpacePosition.Back) + HexUtil.ToHexString(BigIntegerToByteArray(ecPub1.Q.YCoord.ToBigInteger(), bytelength), HexUtil.SpacePosition.Back);
            return sm2V1Key;

            //Encoding.Default.GetString(Hex.Encode(ecPriv1.D.ToByteArray())).ToUpper()
            //Encoding.Default.GetString(Hex.Encode(ecPub1.Q.GetEncoded())).ToUpper()
        }

        public string CompressPubKey(string longPK)
        {
            if (string.IsNullOrWhiteSpace(longPK) || longPK.Length != 128)
            {
                throw new Exception("public key length is not 128!");
            }

            ECCurve curve = GetBaseCurve();
            //byte[] longPKbt = HexStrToToByte("04" + longPK);
            //ECPoint g = curve.DecodePoint(longPKbt);
            //string compressPK = byteToHexStr(g.GetEncoded(true));
            var publicKeyPoint = curve.CreatePoint(new BigInteger(longPK.Substring(0, 64), 16), new BigInteger(longPK.Substring(64, 64), 16), true);
            string compressPK = HexUtil.ToHexString(publicKeyPoint.GetEncoded(true), HexUtil.SpacePosition.Back);
            return compressPK;
        }

        public string UnCompressPubKey(string compressedPK)
        {
            if (string.IsNullOrWhiteSpace(compressedPK) || compressedPK.Length != 66)
            {
                throw new Exception("public key length is not 66!");
            }
            ECCurve curve = GetBaseCurve();
            byte[] compressedPKbt = HexStrToToByte(compressedPK);
            ECPoint eee = curve.DecodePoint(compressedPKbt);
            //string longPK = eee.XCoord.ToString().ToUpper() + eee.YCoord.ToString().ToUpper();
            int bytelength = (int)Math.Ceiling((double)(((double)curve.FieldSize) / 8.0));
            string longPK = HexUtil.ToHexString(BigIntegerToByteArray(eee.XCoord.ToBigInteger(), bytelength), HexUtil.SpacePosition.Back) + HexUtil.ToHexString(BigIntegerToByteArray(eee.YCoord.ToBigInteger(), bytelength), HexUtil.SpacePosition.Back);

            return longPK;
        }

        private static ECCurve GetBaseCurve()
        {
            BigInteger SM2_ECC_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16);
            BigInteger SM2_ECC_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16);
            BigInteger SM2_ECC_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16);
            BigInteger SM2_ECC_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16);
            BigInteger SM2_ECC_H = BigInteger.ValueOf(4);
            return new FpCurve(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H);
        }

        public void Dispose()
        {

        }


        private static byte[] HexStrToToByte(string hexString)
        {
            hexString = hexString.Replace(" ", "");
            if ((hexString.Length % 2) != 0)
                hexString += " ";
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            return returnBytes;
        }

        private static string byteToHexStr(byte[] bytes, int length)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            if (bytes != null)
            {
                for (int i = 0; i < length; i++)
                {
                    sb.Append(bytes[i].ToString("X2"));
                }
            }
            return sb.ToString();
        }

        private static BigInteger[] Decode(byte[] sig)
        {
            Asn1Sequence s = Asn1Sequence.GetInstance(sig);

            return new BigInteger[] { DerInteger.GetInstance(s[0]).Value,
                DerInteger.GetInstance(s[1]).Value };
        }
        private byte[] BigIntegerToByteArray(BigInteger bi)
        {
            return BigIntegerToByteArray(bi, this._byteLen);
        }
        private static byte[] BigIntegerToByteArray(BigInteger bi, int byteLength)
        {
            byte[] sourceArray = bi.ToByteArrayUnsigned();
            if (sourceArray.Length >= byteLength)
            {
                return sourceArray;
            }
            byte[] destinationArray = new byte[byteLength];
            Array.Copy(sourceArray, 0, destinationArray, byteLength - sourceArray.Length, sourceArray.Length);
            return destinationArray;
        }
    }
}
